1. Can we use Jest or Enzyme?

Hooks can only be called inside the body of a function component. (https://fb.me/react-invalid-hook-call)
The error above means that Hooks are not yet supported in Enzyme as seen in this issue here.
As a result, we cannot use Enzyme to carry out component tests for React Hooks. So what can be used?

2. Introducing react-testing-library

react-testing-library is a very light-weight solution for testing React components. It extends upon react-dom and react-dom/test-utils to provide light utility functions. It encourages you to write tests that closely resemble how your react components are used.

3. Installation

This module is distributed via npm which is bundled with node and should be installed as one of your project’s devDependencies:

1
npm install --save-dev @testing-library/react

This library has peerDependencies listings for react and react-dom.

4. Suppressing unnecessary warnings on React DOM 16.8

There is a known compatibility issue with React DOM 16.8 where you will see the following warning:

1
Warning: An update to ComponentName inside a test was not wrapped in act(...).

If you cannot upgrade to React DOM 16.9, you may suppress the warnings by adding the following snippet to your test configuration learn more

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// this is just a little hack to silence a warning that we'll get until we
// upgrade to 16.9: https://github.com/facebook/react/pull/14853
const originalError = console.error
beforeAll(() => {
console.error = (...args) => {
if (/Warning.*not wrapped in act/.test(args[0])) {
return
}
originalError.call(console, ...args)
}
})

afterAll(() => {
console.error = originalError
})

4. configuring some import

1
2
import '@testing-library/react/cleanup-after-each' // deprecated
import '@testing-library/jest-dom/extend-expect' // not work in jest config file when use typescript

these imports are something you’d normally configure Jest to import for you.
automatically. Learn more in the setup docs: https://testing-library.com/docs/react-testing-library/setup#cleanup

sample test Header

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import * as React from "react";
import {useState} from "react";

const Header = () => {
const [value, setValue] = useState<string>("");
const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setValue(event.target.value)
}
return (
<div className="header">
<div className="header-content">
TodoList
<input
value={value}
data-testid="header_input"
onChange={handleInputChange}
/>
</div>
</div>
)
}

export default Header;

// Header.test.tsx
import * as React from "react";
import { render, fireEvent } from "@testing-library/react";
import "@testing-library/jest-dom/extend-expect";

import Header from "../Header";

let wrapper = null;

describe("test Header component", () => {
beforeEach(() => {
wrapper = render(<Header />);
})

it("should contain title 'TodoList'", () => {
const {getByText} = wrapper;
const title = "TodoList";
expect(getByText(title)).toBeInTheDocument();
})

it("input initial value should be empty", () => {
const {getByTestId} = wrapper;
expect(getByTestId("header_input").value).toEqual("");
})

it("value should be set when input some text", () => {
const {getByTestId} = wrapper;
const inputElem = getByTestId("header_input");
const context = "input text test";
fireEvent.change(inputElem, {target: {
value: context
}});
expect(inputElem.value).toEqual(context);
})
})